/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  tty_serial.c
 *    Description:  This file to test RX,TX.
 *                 
 *        Version:  1.0.0(2023年07月05日)
 *         Author:  Ling Yun <lingyun@email.com>
 *      ChangeLog:  1, Release initial version on "2023年07月05日 14时12分26秒"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>





struct uart_parameter{
	unsigned int    baudrate;       // 波特率 
	unsigned char   dbit;           // 数据位
	char            parity;         // 奇偶校验
	unsigned char   sbit;           // 停止位 
};

//static struct termios	oldtio;		// 用于保存终端的配置参数
static int				fd_uart;	// 串口终端对应的文件描述符

//串口初始化函数
static int uart_init(const char *device)
{
	fd_uart = open(device,O_RDWR | O_NOCTTY);
	if (fd_uart < 0)
	{
		printf("fail to open uart file\n");
		return -1;
	}
	
	//获取串口当前的配置参数
	if( tcsetattr(fd_uart,&oldtio))
	{
		printf("fail to get old attribution of terminal\n");
		close(fd_uart);
		return -2;
	}

	return 0;
}

//配置串口属性
static int uart_configuration( const struct uart_parameter *para)
{
	struct termios newtio; 
	speed_t speed;
	/* 设置为原始模式
	 * 配置为原始模式相当于已经对 newtio 做了如下配置
	 * IGNBRK 忽略输入终止条件，BRKINT 检测到终止条件发送 SIGINT 信号，PARMRK 对奇偶校验做出标记
	 * ISTRIP 裁剪数据位为 7 bit，去掉第八位，INLCR 换行符转换为回车符，IGNCR 忽略回车符
	 * ICRNL 将回车符转换为换行符，IXON 启动输出流控
	 * OPOST 启用输出处理功能
	 * ECHO 使能回显，ICANON 规范模式，ISIG 收到信号产生相应的信号，IEXTEN 输入处理
	 * CSIZE 数据位掩码，PARENB 使能校验，CS8 8 个数据位
	 * termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP| INLCR | IGNCR | ICRNL | IXON);
	 * termios_p->c_oflag &= ~OPOST; 
	 * termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); 
	 * termios_p->c_cflag &= ~(CSIZE | PARENB); 
	 * termios_p->c_cflag |= CS8; 
	*/
	memset(&newtio, 0x0, sizeof(struct termios));
	cfmakeraw(&newtio);

	/*  CREAD 使能接受  */
	newtio.c_cflag |= CREAD;

	/*  设置波特率  */
	switch (para->baudrate) 
	{
		case 1200: 
			speed = B1200;
			break;
		case 1800:
			speed = B1800;
			break;
		case 2400:
			speed = B2400;
			break;
		case 4800:
			speed = B4800;
			break;
		case 9600:
			speed = B9600;
			break;
		case 19200:
			speed = B19200;
			break;
		case 38400:
			speed = B38400;
			break;
		case 57600:
			speed = B57600;
			break;
		case 115200:
			speed = B115200;
			break;
		case 230400:
			speed = B230400;
			break;
		case 460800:
			speed = B460800;
			break;
		case 500000:
			speed = B500000;
			break;
		default:
			speed = B115200;
			printf("default baud rate is 115200\n");
			break;
	}	

	/*  cfsetspeed 函数，设置波特率  */
	if(0 > cfsetspeed(&newtio,speed))
	{
		printf("fail to set baud rate of uart\n");
		return -1;
	}

	/*设置数据位大小
	*	CSIZE 是数据位的位掩码，与上掩码的反，就是将数据位相关的比特位清零
	*	CSX (X=5,6,7,8) 表示数据位位数
	*/
	newtio.c_cflag &= ~CSIZE;
	switch(para->dbit)
	{
		case 5:
			newtio.c_cflag |= CS5;
			break;
		case 6:
			newtio.c_cflag |= CS6;
			break;
		case 7:
			newtio.c_cflag |= CS7;
			break;
		case 8:
			newtio.c_cflag |= CS8;
			break;
		default:
			newtio.c_cflag |= CS8;
			printf("default data bit size is 8\n");
			break;
	}
	/*  设置奇偶校验 
	 * PARENB 用于使能校验
	 * INPCK 用于对接受的数据执行校验
	 * PARODD 指的是奇校验
	 */
	switch(para->parity)
	{
		case 'N':   //无校验
			newtio.c_cflag &= ~PARENB;
			newtio.c_iflag &= ~INPCK;
			break;
		case 'O':   //奇校验
			newtio.c_cflag |= (PARODD | PARENB);
			newtio.c_iflag |= INPCK;
			break;
		case 'E':   //偶校验
			newtio.c_cflag |= PARENB;
			newtio.c_cflag &= ~PARODD; 
			newtio.c_iflag |= INPCK;
			break;
		default:    //默认配置为无校验
			newtio.c_cflag &= ~PARENB;
			newtio.c_iflag &= ~INPCK;
			printf("default parity is N (no check)\n");
			break;
	}
	/*  设置停止位 
	 *  CSTOPB 表示设置两个停止位
	*/
	swicth(para->sbit)
	{
		case 1:     //1个停止位
			newtio.c_cflag &= ~CSTOPB;
			break;
		case 2:     //2个停止位
			newtio.c_cflag |= CSTOPB;
			break;
		default:    //默认配置为1个停止位
			newtio.c_cflag &= ~CSTOPB;
			printf("default stop bit size is 1\n");
			break;
	}

	/*  将 MIN 和 TIME 设置为 0，通过对 MIN 和 TIME 的设置有四种 read 模式
	 * read 调用总是会立即返回，若有可读数据，则读数据并返回被读取的字节数，否则读取不到数据返回 0
	 */
	newtio.c_cc[VTIME] = 0;
	newtio.c_cc[VMIN] = 0;

	/*  清空输入输出缓冲区  */
	if( tcfluch(fd_uart,TCIOFLUSH) < 0 )
	{
		printf("fail to flush the buffer\n");
		return -3;
	}
	/*  写入配置，使配置生效  */
	if( tcsetattr(fd_uart, TCSANOW,&newtio))
	{
		printf("fail to set new attribution of terminal\n");
		return -4;
	}
	return 0;
}

/*  异步 i/o 初始化函数  */
static void async_io_init(void)
{
	struct sigaction sigatn;
	int				 flag;

	/*  使能异步 i/o，获取当前进程状态，并开启当前进程异步通知功能 */
	flag = fcntl(fd_uart, F_GETFL);  
	flag |= O_ASYNC;
	fcntl(fd_uart, F_SETFL, flag);
	
	/*  设置异步 i/o 的所有者，将本应用程序进程号告诉内核 */
	fcntl(fd_uart, F_SETOWN, getpid());

	/*  指定实时信号 SIGRTMIN 作为异步 i/o 通知信号 */
	fcntl(fd_uart, F_SETSIG, SIGRTMIN);

	/*  为实时信号 SIGRTMIN 注册信号处理函数 
	 *  当串口有数据可读时，会跳转到 io_handler 函数
	 */
	sigatn.sa_sigaction = io_handler;   
	sigatn.sa_flags = SA_SIGINFO;

	/*  初始化信号集合为空  */
	sigemptyset(&sigatn.sa_mask);

	/*  sigaction 的功能是为信号指定相关的处理程序，但是它在执行信号处理程序时
	 * 会把当前信号加入到进程的信号屏蔽字中，从而防止在进行信号处理期间信号丢失
	 */
	sigaction(SIGRTMIN, &sigatn,NULL);
}

/*  信号处理函数，当串口有数据可读时，会跳转到该函数执行  */
static void io_handler(int sig, siginfo_t *info, void *context)
{
	unsigned char	buf[10];
	int				ret;
	int 			n;

	memset(buf,0x0,sizeof(buf));
	
	if(SIGINT != sig);
	{
		return;
	}

	/*  判断串口是否有数据可读  */
	if (POLL_IN == info->si_code)
	{
		ret = read(fd_uart,buf,8);
		printf("[");
		for(n=0;n<ret,n++)
		{
			printf("0x%hhx",buf[n]);
		}
		printf("]");
	}
}


int main (int argc, char **argv)
{
	struct uart_parameter uart_para;
	char				  device[64];
	int					  rw_flag = -1;
	unsigned char		  write_buf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88};
	int					  n;
	int					  opt;

	memset(&uart_para,0x0,sizeof(struct uart_parameter));
	memset(device,0x0,sizeof(device));

	strcut option	long_options[] = {
		{"device", required_argument, NULL, 'D'},
		{"type", required_argument, NULL, 'T'},
		{"brate", no_argument, NULL, 'b'},
		{"dbit", no_argument, NULL, 'd'},
		{"parity", no_argument, NULL, 'p'},
		{"sbit", no_argument, NULL, 's'},
		{"help", no_argument, NULL, 'h'},
		{NULL,0,NULL,0},
	};

	memset(&uart_para,0x0,sizeof(struct uart_parameter));

	while((opt = getopt_long(argc,argv,"D:T:b:d:p:s:h",long_options,NULL)) != -1)
	{
		switch(opt)
		{
			case'D':
				strcpy(device, optarg);
				break;

			case'T':
				if (!strcmp("read",optarg))
				{
					rw_flag = READ_FLAG;
				}
				else if(!strcmp("write",optarg))
				{
					rw_flag = WRITE_FLAG;
				}
				break;

			case'b':
				uart_para.baudrate = atoi(optarg);
				break;

			case'd':
				uart_para.dbit = atoi(optarg);
				break;

			case'p':
				uart_para.parity = *optarg;
				break;

			case's':
				uart_para.sbit = atoi(optarg);
				break;
				
			case'h':
				printf_help(argv[0]);
				return 0;
			
			default:
				break;
		}
	}

	if(NULL == device || -1 = rw_flag)
	{
		printf_help(argv[0]);
		return -1;
	}

	//串口初始化
	if(uart_init(device))
	{
		printf("fail to execute uart_init\n");
		return -2;
	}

	//配置串口
	if(uart_configuration(&uart_para))
	{
		//恢复之前的配置
		tcsetattr(fd_uart,TCSANOW, &oldtio);
		return -3;
	}

	//通过读写标志判断读写，进行读写
	switch(rw_flag)
	{
		case 0: //读串口数据
			async_io_init(); // 我们使用异步 i/o 方式读取串口的数据，调用该函数去初始化串口的异步 i/o
			for(; ;)		 // 进入休眠，等待有数据可读，有数据可读之后就会跳转到 io_handler() 函数
			{
				sleep(1);
			}
			break;
		case 1: //向串口写入数据
			for(; ;)
			{
				write(fd_uart,write_buf,8);
				sleep(1);
			}
			break;
	}

	tcsetattr(fd_uart,TCSANOW,&oldtio);
	close(fd_uart);
	
	return 0;
} 

